<?php
namespace app\api\controller;

use app\api\model\OrderModel;
use app\api\model\PaymentConfigModel;
use think\Exception;

header("Content-type:text/html;charset=utf-8"); //此处进行字符集初始化
/**
 * 小程序支付接口
 * Class Pay
 * @package app\api\controller
 */
class Pay extends Authbase
{
    /**
     * 小程序支付
     * @return \think\response\Json
     */
    public function pay()
    {
        $money = input('money', 0, 'trim');
        if (empty($money)) {
            return json(['code' => 0, 'error' => 'money不能为零']);
        }

        try {
            $user = $this->useMode;
            // 获取支付配置
            $payment = PaymentConfigModel::getOne(['applet_app_id' => $user['applet_id']]);
            $mch_id = $payment['mch_id'];
            $app_id = $payment['app_id'];

            $trade_no = OrderModel::generateTradeNo($this->Auth_Wxuser_Id);
            $result = $this->requestUnifiedOrder($money, $trade_no, $app_id, $mch_id);
            // 判断返回结果
            if ($result['RETURN_CODE'] == 'SUCCESS') {
                $time = time();
                $info = [
                    'appId' => $app_id,
                    'timeStamp' => "" . $time . "",
                    'nonceStr' => $this->nonce_str(),
                    'package' => 'prepay_id=' . $result['PREPAY_ID'],
                    // 统一下单接口返回的prepay_id参数值, 提交格式如: prepay_id=*
                    'signType' => 'MD5', //签名算法, 暂支持MD5
                ];
                $info ['paySign'] = $this->MakeSign($info);
                // 生成订单
                $this->createOrder($trade_no, $money);
                return json(['code' => 1, 'msg' => 'success', 'data' => $info]);
            } else {
                $msg = $result['RETURN_MSG']? $result['RETURN_MSG']: '统一下单请求失败！';
                throw new Exception($msg);
            }
        }catch(\Exception $e) {
            return json(['code' => 0, 'error' => $e->getMessage()]);
        }
    }

    /**
     * 生成订单
     * @param $trade_no
     * @param $money
     * @return string
     * @throws Exception
     */
    private function createOrder($trade_no, $money) {
        //流水号
        $user = $this->useMode;
        $order_data = [
            'trade_no' => $trade_no,
            'open_id' => $user['openid'],
            'applet_app_id' => $user['applet_id'],
            'nick_name' => $user['nick_name'],
            'applet_name' => $user['applet_name'],
            'money' => $money,
            'created_time' => date('Y-m-d H:i:s', time()),
        ];
        $orderM = new OrderModel();
        $order = $orderM->save($order_data);
        if (!$order) {
            throw new Exception('订单生成失败');
        }
    }

    /**
     * 请求微信统一下单接口
     *
     * @param $money
     * @param $trade_no
     * @param string $app_id
     * @param string $mch_id
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    private function requestUnifiedOrder($money, $trade_no, $app_id, $mch_id) {
        $data = array(
            'appid' => $app_id, //小程序appid
            'body' => '打赏', //商品描述
            'mch_id' => $mch_id, //商户号
            'nonce_str' => $this->nonce_str(), //随机字符串
            'notify_url' => 'http://mv.yqwaijia.cn/index.php/api/pay_call_back/index', //通知地址 http://mv.yqwaijia.cn/api/pay/call_back
            'out_trade_no' => $trade_no, //商户订单号
            'openid' => $this->Auth_Wxuser_OpenId, //用户openid
            'spbill_create_ip' => '', //终端IP
            'total_fee' => $money*100, //标价金额
            'trade_type' => 'JSAPI', //交易类型
        );
        $sign = $this->sign($data); //签名
        $data['sign'] = $sign;
        $xmldata = $this->xml($data); //将数组转化为XML
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $res = $this->http_request($url, $xmldata); //调用支付接口
        return $result = $this->getxml($res); //将XML转化为数组
    }

    /**
     * 返回xml数据
     * @param $data
     * @return string
     */
    public function xml($data)
    {
        ksort($data);
        // 拼接数据
        $data_xml = "<xml>";
        foreach ($data as $k=>$v) {
            if (is_numeric($v)) {
                $data_xml .= "<" .$k. ">" .$v. "</" .$k. ">";
            } else {
                $data_xml .= "<" .$k. "><![CDATA[" .$v. "]]></" .$k. ">";
            }
        }
        $data_xml .= "</xml>";
        return $data_xml;
    }

    /**
     * 获取xml
     * @param $xml
     * @return array
     */
    private function getxml($xml)
    {
        $p = xml_parser_create();
        xml_parse_into_struct($p, $xml, $vals, $index);
        xml_parser_free($p);
        $data = [];
        foreach ($index as $key=>$value) {
            if ($key == 'xml' || $key == 'XML') continue;
            $tag = $vals[$value[0]]['tag'];
            $value = $vals[$value[0]]['value'];
            $data[$tag] = $value;
        }
        return $data;
    }

    /**
     * curl 请求
     * @param $url
     * @param $rawData
     * @return mixed
     */
    public function http_request($url, $rawData)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $rawData);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text'));
        $data = curl_exec($ch);
        curl_close($ch);
        return ($data);
    }

    /**
     * 随机32位字符串
     * @return string
     */
    public function nonce_str()
    {
        $result = '';
        $str = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklmnbvcxz';
        for ($i=0; $i<32; $i++) {
            $result .= $str[mt_rand(0, 48)];
        }
        return $result;
    }

    /**
     * 计算签名
     * @param $data
     * @return string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function sign($data)
    {
        // 获取支付配置的key
        $user = $this->useMode;
        // 获取支付配置
        $key = PaymentConfigModel::getKeyByAppletId( $user['applet_id']);

        // 字典序排列
        ksort($data);
        $buff = '';
        foreach ($data as $k=>$v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k. '=' .$v. '&';
            }
        }
        $buff = trim($buff, '&');
        // 拼接key
        $string = $buff. '&key=' . $key;
        // md5加密
        $string = md5($string);
        // 转为大写
        $sign = strtoupper($string);
        return $sign;
    }

    /**
     * 签名
     * @param $params
     * @return string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function MakeSign( $params )
    {
        // 获取支付配置的key
        $user = $this->useMode;
        // 获取支付配置
        $key = PaymentConfigModel::getKeyByAppletId( $user['applet_id']);

        $params = array_filter($params);
        //签名步骤一：按字典序排序数组参数
        ksort($params);
        $str = '';
        foreach ($params as $k => $v) {
            $str .= $k . '=' . $v . '&';
        }
        //签名步骤二：在string后加入KEY
        $str .= 'key=' . $key;
        //签名步骤三：MD5加密
        $string = md5($str);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }
}